package com.studynotes.dev.tech.common.utils;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.ReflectUtil;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.metadata.OrderItem;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.google.common.collect.Lists;
import com.studynotes.dev.tech.annotation.Crypto;
import com.studynotes.dev.tech.annotation.query.QueryColumn;
import com.studynotes.dev.tech.common.base.BasePage;
import com.studynotes.dev.tech.common.enums.QueryMethodEnum;
import lombok.SneakyThrows;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.util.ReflectionUtils;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Parameter;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;

/**
 * @author Liu XiaoJun
 * @description: QueryWrapper工具类
 * @date 2025-11-07 14:23:30
 */
public class QueryWrapperUtil {

    public static final Integer DEFAULT_PAGE = 1;
    public static final Integer DEFAULT_SIZE = 10;
    public static final String DEFAULT_SORT_COLUMN = "id";
    public static final Integer SORT_BY_ASC = 1;
    public static final Integer SORT_BY_DESC = -1;
    private static final Logger logger;

    public static final String PAGE = "p";

    public static final String SIZE = "s";

    public static final String ORDER = "o";

    public static final String COLUMN = "c";

    private QueryWrapperUtil() {
        throw new IllegalStateException("Utility class");
    }

    static {
        logger = LoggerFactory.getLogger(QueryWrapperUtil.class);
    }

    /**
     * ofPage
     *
     * @param source source
     * @param <T> T
     * @return IPage
     */
    public static <T> IPage<T> ofPage(Object source) {
        return ofPage(source, null);
    }

    /**
     * ofPage
     *
     * @param source source
     * @param prefix prefix
     * @param <T> T
     * @return IPage
     */
    public static <T> IPage<T> ofPage(Object source, String prefix) {

        int page = DEFAULT_PAGE;
        int size = DEFAULT_SIZE;
        Object p = ObjectUtil.isNotNull(source) ? BeanUtil.getProperty(source, PAGE) : null;
        if (p != null) {
            page = Integer.parseInt(p.toString());
        }
        Object s = ObjectUtil.isNotNull(source) ? BeanUtil.getProperty(source, SIZE) : null;
        if (s != null) {
            size = Integer.parseInt(s.toString());
        }
        Page<T> tPage = new Page<>(page, size);
        List<OrderItem> orderItems = ofOrders(source, prefix);
        tPage.addOrder(orderItems);
        return tPage;
    }

    /**
     * ofPage
     *
     * @param source source
     * @param <T> T
     * @return IPage
     */
    public static <T> IPage<T> ofPage(BasePage source) {
        return ofPage(source, null);
    }

    /**
     * ofPage
     *
     * @param source source
     * @param prefix prefix
     * @param <T> T
     * @return IPage
     */
    public static <T> IPage<T> ofPage(BasePage source, String prefix) {

        int page = Optional.ofNullable(source.getPageNum()).orElse(DEFAULT_PAGE);
        int size = Optional.ofNullable(source.getPageSize()).orElse(DEFAULT_SIZE);
        Page<T> tPage = new Page<>(page, size);
        List<OrderItem> orderItems = ofOrders(source, prefix);
        tPage.addOrder(orderItems);
        return tPage;
    }

    /**
     * ofOrders
     *
     * @param source source
     * @return List of OrderItem
     */
    public static List<OrderItem> ofOrders(Object source) {
        return ofOrders(source, null);
    }

    /**
     * ofOrders
     *
     * @param source source
     * @param prefix prefix
     * @return List of OrderItem
     */
    public static List<OrderItem> ofOrders(Object source, String prefix)
    {
        String column = DEFAULT_SORT_COLUMN;
        Integer order = SORT_BY_DESC;
        Object o = ObjectUtil.isNotNull(source) ? BeanUtil.getProperty(source, ORDER) : null;
        if (Objects.nonNull(o)) {
            order = Integer.valueOf(o.toString());
        }
        Object c = ObjectUtil.isNotNull(source) ? BeanUtil.getProperty(source, COLUMN) : null;
        if (Objects.nonNull(c)) {
            column = c.toString();
        }
        if (ObjectUtil.isNotEmpty(prefix)) {
            column = prefix +"."+ column;
        }
        OrderItem orderItem = SORT_BY_ASC.equals(order) ? OrderItem.asc(column) : OrderItem.desc(column);
        return Collections.singletonList(orderItem);
    }

    /**
     * queryToWrapper
     *
     * @param source source
     * @param targetClass target class
     * @param <T> T
     * @return QueryWrapper
     */
    public static <T> QueryWrapper<T> queryToWrapper(Object source, Class<T> targetClass) {
        return queryToWrapper(source, targetClass, null);
    }

    /**
     *
     * @param source source source
     * @param targetClass class type
     * @param prefix prefix prefix
     * @param <T> T
     * @return instance of QueryWrapper
     */
    @SneakyThrows
    @SuppressWarnings("all")
    public static <T> QueryWrapper<T> queryToWrapper(Object source, Class<T> targetClass, String prefix) {
        if (source == null) {
            return Wrappers.emptyWrapper();
        }
        Class<?> actualEditable = source.getClass();
        PropertyDescriptor[] sourcePds = BeanUtils.getPropertyDescriptors(actualEditable);
        QueryWrapper<T> queryWrapper = Wrappers.query(targetClass.newInstance());
        for(PropertyDescriptor sourcePd: sourcePds) {
            if (sourcePd.getReadMethod() == null) {
                continue;
            }
            String property  = sourcePd.getName();
            AtomicReference<String> column = new AtomicReference<>("");
            Field sourceField = ReflectUtil.getField(actualEditable, property);
            if (ObjectUtil.isNull(sourceField)) {
                continue;
            }
            Method readMethod = sourcePd.getReadMethod();
            if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {
                ReflectionUtils.makeAccessible(readMethod);
            }
            Class<?> valueType =readMethod.getReturnType();
            Object value = readMethod.invoke(source);
            if (ObjectUtil.isEmpty(value)) {
                logger.debug("value is null");
                continue;
            }
            Field field = ReflectUtil.getField(targetClass, property);
            if (ObjectUtil.isNull(field)) {
                continue;
            }
            QueryMethodEnum queryMethod = QueryMethodEnum.EQ;
            QueryColumn queryColumn = sourceField.getAnnotation(QueryColumn.class);
            if (queryColumn != null) {
                queryMethod = queryColumn.method();
                column.set(queryColumn.column());
            }
            TableField tableField = field.getAnnotation(TableField.class);
            Crypto crypto = field.getAnnotation(Crypto.class);
            buildColumn(prefix, property, column, tableField);
            List<Object> args = buildArgs(column, valueType, value, queryMethod, Optional.ofNullable(crypto).isPresent());
            queryMethod.invoke(queryWrapper, args.toArray());
        }
        return queryWrapper;
    }

    private static void buildColumn(String prefix, String property, AtomicReference<String> column, TableField tableField) {
        if (CharSequenceUtil.isBlank(column.get())) {
            if (tableField != null) {
                column.set(tableField.value());
            } else {
                column.set(CharSequenceUtil.toUnderlineCase(property));
            }
        }
        if (ObjectUtil.isNotEmpty(prefix)) {
            column.set(prefix + "." + column.get());
        }
    }

    private static List<Object> buildArgs(AtomicReference<String> column, Class<?> valueType, Object value, QueryMethodEnum queryMethod, boolean isCrypto) {
        List<Object> args = new ArrayList<>();
        args.add(column.get());
        if (valueType.equals(List.class)) {
            List<Object> values = OptionalUtil.ofBooleanForResult(isCrypto, () -> CollectionUtil.toList((List<Object>) value, item -> FieldCryptoUtil.encrypt(String.valueOf(item))), () -> (List<Object>) value);
            if (queryMethod.equals(QueryMethodEnum.BETWEEN)) {
                args.addAll(values);
            } else {
                args.add(values.toArray());
            }
        } else {
            Object o = OptionalUtil.ofBooleanForResult(isCrypto, () -> FieldCryptoUtil.encrypt(String.valueOf(value)), () -> value);
            Parameter[] parameters = queryMethod.getMethod().getParameters();
            Parameter parameter = parameters[1];
            Class<?> targetType = parameter.getType();
            if (targetType.equals(Object[].class)) {
                args.add(Lists.newArrayList(o).toArray());
            } else {
                args.add(o);
            }
        }
        return args;
    }

    public static <T> void parseOrderColumn(BasePage source, Class<T> target) {
        if (CharSequenceUtil.isBlank(source.getOrderByColumn())) {
            Field[] fields = ReflectUtil.getFields(target);
            Optional<Field> field = Arrays.stream(fields).filter(f -> f.isAnnotationPresent(TableId.class)).findFirst();
            if (field.isPresent()) {
                TableId tableId = field.get().getAnnotation(TableId.class);
                if(Objects.nonNull(tableId.value())){
                    source.setOrderByColumn(tableId.value());
                }
            }
        }
    }

    public static <T> void parseOrderColumn(Object source, Class<T> target) {
        Object c = ObjectUtil.isNotNull(source) ? BeanUtil.getProperty(source, COLUMN) : null;
        if (Objects.isNull(c) || CharSequenceUtil.isBlank(c.toString())) {
            Field[] fields = ReflectUtil.getFields(target);
            Optional<Field> field = Arrays.stream(fields).filter(f -> f.isAnnotationPresent(TableId.class)).findFirst();
            if (field.isPresent()) {
                TableId tableId = field.get().getAnnotation(TableId.class);
                if(Objects.nonNull(tableId.value())){
                    BeanUtil.setProperty(source, COLUMN, tableId.value());
                }
            }
        }
    }

}
